/*
 * Copyright (C) 2007 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.base;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A reference queue with an associated background thread that dequeues
 * references and invokes {@link FinalizableReference#finalizeReferent()} on
 * them.
 * 
 * <p>
 * Keep a strong reference to this object until all of the associated referents
 * have been finalized. If this object is garbage collected earlier, the backing
 * thread will not invoke {@code finalizeReferent()} on the remaining
 * references.
 * 
 * @author Bob Lee
 * @since 2010.01.04 <b>stable</b> (imported from Google Collections Library)
 */
public class FinalizableReferenceQueue {

    /*
     * The Finalizer thread keeps a phantom reference to this object. When the
     * client (ReferenceMap, for example) no longer has a strong reference to
     * this object, the garbage collector will reclaim it and enqueue the
     * phantom reference. The enqueued reference will trigger the Finalizer to
     * stop.
     * 
     * If this library is loaded in the system class loader,
     * FinalizableReferenceQueue can load Finalizer directly with no problems.
     * 
     * If this library is loaded in an application class loader, it's important
     * that Finalizer not have a strong reference back to the class loader.
     * Otherwise, you could have a graph like this:
     * 
     * Finalizer Thread runs instance of -> Finalizer.class loaded by ->
     * Application class loader which loaded -> ReferenceMap.class which has a
     * static -> FinalizableReferenceQueue instance
     * 
     * Even if no other references to classes from the application class loader
     * remain, the Finalizer thread keeps an indirect strong reference to the
     * queue in ReferenceMap, which keeps the Finalizer running, and as a
     * result, the application class loader can never be reclaimed.
     * 
     * This means that dynamically loaded web applications and OSGi bundles
     * can't be unloaded.
     * 
     * If the library is loaded in an application class loader, we try to break
     * the cycle by loading Finalizer in its own independent class loader:
     * 
     * System class loader -> Application class loader -> ReferenceMap ->
     * FinalizableReferenceQueue -> etc. -> Decoupled class loader -> Finalizer
     * 
     * Now, Finalizer no longer keeps an indirect strong reference to the static
     * FinalizableReferenceQueue field in ReferenceMap. The application class
     * loader can be reclaimed at which point the Finalizer thread will stop and
     * its decoupled class loader can also be reclaimed.
     * 
     * If any of this fails along the way, we fall back to loading Finalizer
     * directly in the application class loader.
     */

    private static final Logger logger = Logger
            .getLogger(FinalizableReferenceQueue.class.getName());

    private static final String FINALIZER_CLASS_NAME = "com.google.common.base.internal.Finalizer";

    /** Reference to Finalizer.startFinalizer(). */
    private static final Method startFinalizer;
    static {
        Class<?> finalizer = loadFinalizer(new SystemLoader(),
                new DecoupledLoader(), new DirectLoader());
        startFinalizer = getStartFinalizer(finalizer);
    }

    /**
     * The actual reference queue that our background thread will poll.
     */
    final ReferenceQueue<Object> queue;

    /**
     * Whether or not the background thread started successfully.
     */
    final boolean threadStarted;

    /**
     * Constructs a new queue.
     */
    @SuppressWarnings("unchecked")
    public FinalizableReferenceQueue() {
        // We could start the finalizer lazily, but I'd rather it blow up early.
        ReferenceQueue<Object> queue;
        boolean threadStarted = false;
        try {
            queue = (ReferenceQueue<Object>) startFinalizer.invoke(null,
                    FinalizableReference.class, this);
            threadStarted = true;
        } catch (IllegalAccessException e) {
            // Finalizer.startFinalizer() is public.
            throw new AssertionError(e);
        } catch (Throwable t) {
            logger.log(
                    Level.INFO,
                    "Failed to start reference finalizer thread."
                            + " Reference cleanup will only occur when new references are"
                            + " created.", t);
            queue = new ReferenceQueue<Object>();
        }

        this.queue = queue;
        this.threadStarted = threadStarted;
    }

    /**
     * Repeatedly dequeues references from the queue and invokes
     * {@link FinalizableReference#finalizeReferent()} on them until the queue
     * is empty. This method is a no-op if the background thread was created
     * successfully.
     */
    void cleanUp() {
        if (threadStarted) {
            return;
        }

        Reference<?> reference;
        while ((reference = queue.poll()) != null) {
            /*
             * This is for the benefit of phantom references. Weak and soft
             * references will have already been cleared by this point.
             */
            reference.clear();
            try {
                ((FinalizableReference) reference).finalizeReferent();
            } catch (Throwable t) {
                logger.log(Level.SEVERE, "Error cleaning up after reference.",
                        t);
            }
        }
    }

    /**
     * Iterates through the given loaders until it finds one that can load
     * Finalizer.
     * 
     * @return Finalizer.class
     */
    private static Class<?> loadFinalizer(FinalizerLoader... loaders) {
        for (FinalizerLoader loader : loaders) {
            Class<?> finalizer = loader.loadFinalizer();
            if (finalizer != null) {
                return finalizer;
            }
        }

        throw new AssertionError();
    }

    /**
     * Loads Finalizer.class.
     */
    interface FinalizerLoader {

        /**
         * Returns Finalizer.class or null if this loader shouldn't or can't
         * load it.
         * 
         * @throws SecurityException
         *             if we don't have the appropriate privileges
         */
        Class<?> loadFinalizer();
    }

    /**
     * Tries to load Finalizer from the system class loader. If Finalizer is in
     * the system class path, we needn't create a separate loader.
     */
    static class SystemLoader implements FinalizerLoader {
        public Class<?> loadFinalizer() {
            ClassLoader systemLoader;
            try {
                systemLoader = ClassLoader.getSystemClassLoader();
            } catch (SecurityException e) {
                logger.info("Not allowed to access system class loader.");
                return null;
            }
            if (systemLoader != null) {
                try {
                    return systemLoader.loadClass(FINALIZER_CLASS_NAME);
                } catch (ClassNotFoundException e) {
                    // Ignore. Finalizer is simply in a child class loader.
                    return null;
                }
            } else {
                return null;
            }
        }
    }

    /**
     * Try to load Finalizer in its own class loader. If Finalizer's thread had
     * a direct reference to our class loader (which could be that of a
     * dynamically loaded web application or OSGi bundle), it would prevent our
     * class loader from getting garbage collected.
     */
    static class DecoupledLoader implements FinalizerLoader {

        private static final String LOADING_ERROR = "Could not load Finalizer in"
                + " its own class loader. Loading Finalizer in the current class loader"
                + " instead. As a result, you will not be able to garbage collect this"
                + " class loader. To support reclaiming this class loader, either"
                + " resolve the underlying issue, or move Google Collections to your"
                + " system class path.";

        public Class<?> loadFinalizer() {
            try {
                /*
                 * We use URLClassLoader because it's the only concrete class
                 * loader implementation in the JDK. If we used our own
                 * ClassLoader subclass, Finalizer would indirectly reference
                 * this class loader:
                 * 
                 * Finalizer.class -> CustomClassLoader ->
                 * CustomClassLoader.class -> This class loader
                 * 
                 * System class loader will (and must) be the parent.
                 */
                ClassLoader finalizerLoader = newLoader(getBaseUrl());
                return finalizerLoader.loadClass(FINALIZER_CLASS_NAME);
            } catch (Exception e) {
                logger.log(Level.WARNING, LOADING_ERROR, e);
                return null;
            }
        }

        /**
         * Gets URL for base of path containing Finalizer.class.
         */
        URL getBaseUrl() throws IOException {
            // Find URL pointing to Finalizer.class file.
            String finalizerPath = FINALIZER_CLASS_NAME.replace('.', '/')
                    + ".class";
            URL finalizerUrl = getClass().getClassLoader().getResource(
                    finalizerPath);
            if (finalizerUrl == null) {
                throw new FileNotFoundException(finalizerPath);
            }

            // Find URL pointing to base of class path.
            String urlString = finalizerUrl.toString();
            if (!urlString.endsWith(finalizerPath)) {
                throw new IOException("Unsupported path style: " + urlString);
            }
            urlString = urlString.substring(0, urlString.length()
                    - finalizerPath.length());
            return new URL(finalizerUrl, urlString);
        }

        /** Creates a class loader with the given base URL as its classpath. */
        URLClassLoader newLoader(URL base) {
            return new URLClassLoader(new URL[] { base });
        }
    }

    /**
     * Loads Finalizer directly using the current class loader. We won't be able
     * to garbage collect this class loader, but at least the world doesn't end.
     */
    static class DirectLoader implements FinalizerLoader {
        public Class<?> loadFinalizer() {
            try {
                return Class.forName(FINALIZER_CLASS_NAME);
            } catch (ClassNotFoundException e) {
                throw new AssertionError(e);
            }
        }
    }

    /**
     * Looks up Finalizer.startFinalizer().
     */
    static Method getStartFinalizer(Class<?> finalizer) {
        try {
            return finalizer.getMethod("startFinalizer", Class.class,
                    Object.class);
        } catch (NoSuchMethodException e) {
            throw new AssertionError(e);
        }
    }
}
